package l2hc.gameserver.model.reward;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import l2hc.commons.math.SafeMath;
import l2hc.commons.util.Rnd;
import l2hc.gameserver.Config;
import l2hc.gameserver.model.Player;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RewardGroup implements Cloneable
{
    private static final Logger _log = LoggerFactory.getLogger(RewardGroup.class);

	private double _chance;
	private boolean _isAdena = false; // Шанс фиксирован, растет только количество
	private boolean _notRate = false; // Рейты вообще не применяются
        private boolean _isChampion = false;
        private boolean _isHerb = false;
        private boolean _isRaid = false;
        private boolean _isSealStones = false;
        private boolean _isEpolets = false;
        private boolean _isEnchantScroll = false;
        private boolean _isForgottenScroll = false;
        private boolean _isRecipe = false;
        private boolean _isCodexBook = false;
        private boolean _isAtt = false;
        private boolean _isLifeStone = false;
        private boolean _isKeyMatherial = false;
        private boolean _isEquipment= false;
	private List<RewardData> _items = new ArrayList<RewardData>();
	private double _chanceSum;

	public RewardGroup(double chance)
	{
		setChance(chance);
	}

	public boolean notRate()
	{
		return _notRate;
	}

	public void setNotRate(boolean notRate)
	{
		_notRate = notRate;
	}
	
	public double getChance()
	{
		return _chance;
	}

	public void setChance(double chance)
	{
		_chance = chance;
	}

	public boolean isAdena()
	{
		return _isAdena;
	}
	
	public void setIsAdena(boolean isAdena)
	{
		_isAdena = isAdena;
	}
	
        public boolean isHerb()
	{
		return _isHerb;
	}
	
	public void setIsHerb(boolean isHerb)
	{
		_isHerb = isHerb;
	}
        
        // L2-HC Team
        
         public boolean isSealStones()
	{
		return _isSealStones;
	}
         
         public void setIsSealStones(boolean isSealStones)
	{
		_isSealStones = isSealStones;
	}
        
         public boolean isEpolets()
	{
	         return _isEpolets;
	}
         
         public void setIsEpolets(boolean isEpolets)
	{
		_isEpolets = isEpolets;
	}
	
         public boolean isEnchantScroll()
	{
	      return _isEnchantScroll;
	}
         
         public void setIsEnchantScroll(boolean isEnchantScroll)
	{
		_isEnchantScroll = isEnchantScroll;
	}         
         
         public boolean isForgottenScroll()
	{
	            return _isForgottenScroll;
	}
         
         public void setIsForgottenScroll(boolean isForgottenScroll)
	{
		_isForgottenScroll = isForgottenScroll;
	}
        
         public boolean isRecipe()
	{
	         return _isRecipe;
	}
         
         public void setIsRecipe(boolean isRecipe)
	{
		_isRecipe = isRecipe;
	}
         
         public boolean isCodexBook()
	{
	        return _isCodexBook;
	}         
        
         public void setIsCodexBook(boolean isCodexBook)
	{
		_isCodexBook = isCodexBook;
	}
        
         public boolean isAtt()
	{
	        return _isAtt;
	}
         
         public void setIsAtt(boolean isAtt)
	{
		_isAtt = isAtt;
	}
        
         public boolean isLifeStone()
	{
	         return _isLifeStone;
	}
         
         public void setIsLifeStone(boolean isLifeStone)
	{
		_isLifeStone = isLifeStone;
	}
         
         public boolean isKeyMatherial()
	{
	         return _isKeyMatherial;
	}
         
         public void setIsKeyMatherial(boolean isKeyMatherial)
	{
		_isKeyMatherial = isKeyMatherial;
	}

         public boolean isEquipment()
	{
	         return _isEquipment;
	}
         
         public void setIsEquipment(boolean isEquipment)
	{
		_isEquipment = isEquipment;
	}   

	public void addData(RewardData item)
	{
		if(item.getItem().isAdena())
			setIsAdena(true);
		if(item.getItem().isHerb())
			setIsHerb(true);
                if(item.getItem().isSealStones())
			setIsSealStones(true);
		if(item.getItem().isEpolets())
			setIsEpolets(true);         
                if(item.getItem().isEnchantScroll())
			setIsEnchantScroll(true);
		if(item.getItem().isForgottenScroll())
			setIsForgottenScroll(true);
                if(item.getItem().isRecipe())
			setIsRecipe(true);
		if(item.getItem().isCodexBook())
			setIsCodexBook(true);              
                if(item.getItem().isAtt())
			setIsAtt(true);         
                if(item.getItem().isLifeStone())
			setIsLifeStone(true);
		if(item.getItem().isKeyMatherial()||item.getItem().isCraftMatherial()) // куски и материалы для крафта
			setIsKeyMatherial(true);
                if(item.getItem().isEquipment()||item.getItem().isArmor()||item.getItem().isAccessory()||item.getItem().isWeapon())//начиная со шмоток питомца заканчивая пушками
			setIsEquipment(true);
		_chanceSum += item.getChance();
		item.setChanceInGroup(_chanceSum);
		_items.add(item);
	}

	/**
	 * Возвращает список вещей
	 */
	public List<RewardData> getItems()
	{
		return _items;
	}

	/**
	 * Возвращает полностью независимую копию группы
	 */
	@Override
	public RewardGroup clone()
	{
		RewardGroup ret = new RewardGroup(_chance);
		for(RewardData i : _items)
			ret.addData(i.clone());
		return ret;
	}

	/**
	 * Функция используется в основном механизме расчета дропа, выбирает одну/несколько вещей из группы, в зависимости от рейтов
	 * 
	 */
	public List<RewardItem> roll(RewardType type, Player player, double mod, boolean isRaid, boolean isChampion)
	{
		switch(type)
		{
			case NOT_RATED_GROUPED:
                if(isChampion) {
                    if (!_isAdena && Config.CHAMPION_DROP_ONLY_ADENA) // если не адена и включен конфиг, то возвращаем пустой лист
                        return Collections.emptyList();
                    if (_isHerb && !Config.CHAMPION_DROP_HERBS) // если херб и вкл. конфиг то возвращаем пустой лист
                        return Collections.emptyList();
                }

                if(_isHerb) { //в основном хербы находятся в этой группе
                    return rollItems(1.0, Config.RATE_DROP_HERBS, 1.0);
                }

                return rollItems(mod, 1.0, 1.0);

            case NOT_RATED_NOT_GROUPED:
				return rollItems(mod, 1.0, 1.0);
			case SWEEP:
                if(_isRecipe) {
                    return rollItems(mod, Config.RATE_SPOIL_RECIPES, 1.0);
                }

                if(_isEnchantScroll) {
                    return rollItems(mod, Config.RATE_SPOIL_ENCHANT_SCROLL, 1.0);
                }

                if(_isEquipment) {
                    return rollItems(mod, Config.RATE_SPOIL_EQIPMENT, 1.0);
                }

				return rollItems(mod, Config.RATE_DROP_SPOIL, player.getRateSpoil());
			
            case RATED_GROUPED:
                if(isRaid) {
                    return rollItems(mod, Config.RATE_DROP_RAIDBOSS, 1.0);
                }

                if(isChampion) {
                    if (!_isAdena && Config.CHAMPION_DROP_ONLY_ADENA) {// если не адена и включен конфиг, то возвращаем пустой лист
                        return Collections.emptyList();
                    }

                    if (_isHerb && !Config.CHAMPION_DROP_HERBS) {// если чемпион и вкл. конфиг то возвращаем пустой лист
                        return Collections.emptyList();
                    }
                    // переменная mod находится в скиле чемпиона (id 4407) параметр DropMultiplier
                    // !! mod умножается на RATE_DROP_CHAMPION
                    return rollItems(mod, Config.RATE_DROP_CHAMPION, 1.0);
                }

                if(_isAdena) {
                    return rollAdena(mod, Config.RATE_DROP_ADENA, player.getRateAdena());
                }

                if(_isHerb) {
                    return rollItems(mod, Config.RATE_DROP_HERBS, 1.0);
                }
                if(_isSealStones) {
                    return rollItems(mod, Config.RATE_DROP_SEAL_STONE, 1.0);
                }

                if(_isEnchantScroll) {
                     return rollItems(mod, Config.RATE_DROP_ENCHANT_SCROLL, 1.0);
                }

                if(_isForgottenScroll) {
                    return rollItems(mod, Config.RATE_DROP_FORGOTTEN_SCROLL, 1.0);
                }

                if(_isRecipe) {
                    return rollItems(mod, Config.RATE_DROP_RECIPES, 1.0);
                }

                if(_isCodexBook) {
                    return rollItems(mod, Config.RATE_DROP_CODEX_BOOK, 1.0);
                }

                if(_isAtt) {
                    return rollItems(mod, Config.RATE_DROP_ATT, 1.0);
                }

                if(_isLifeStone) {
                    return rollItems(mod, Config.RATE_DROP_LIFE_STONE, 1.0);
                }

                if(_isEpolets) {
                    return rollItems(mod, Config.RATE_DROP_EPOLETS, 1.0);
                }

                if(_isKeyMatherial) {
                    return rollItems(mod, Config.RATE_DROP_KEY_MATERIAL, 1.0);
                }

                if(_isEquipment) {
                    return rollItems(mod, Config.RATE_DROP_EQUIPMENT, 1.0);
                }
				
				return rollItems(mod, Config.RATE_DROP_ITEMS, player.getRateItems());
			default:
				return Collections.emptyList();
		}
	}

	public List<RewardItem> rollItems(double mod, double baseRate, double playerRate)
	{
		if(mod <= 0)
			return Collections.emptyList();

		double rate;
		if(_notRate)
			rate = Math.min(mod, 1.0);
		else
			rate = baseRate * playerRate * mod;

		double mult = Math.ceil(rate);

		List<RewardItem> ret = new ArrayList<RewardItem>((int) (mult * _items.size()));
        for(long n = 0; n < _items.size(); n++)  // не вижу смысла пускать цикл больше раз чем размер листа итемов
            if(Rnd.get(1, RewardList.MAX_CHANCE) <= _chance * Math.min(Math.ceil(rate) - n, 1.0))  {
                rollFinal(_items, ret, rate, Math.max(_chanceSum, RewardList.MAX_CHANCE));
            }
		return ret;
	}

	private List<RewardItem> rollAdena(double mod, double baseRate, double playerRate)
	{
		double chance = _chance;
		if(mod > 10)
		{
			mod *= _chance / RewardList.MAX_CHANCE;
			chance = RewardList.MAX_CHANCE;
		}

		if(mod <= 0)
			return Collections.emptyList();

		if(Rnd.get(1, RewardList.MAX_CHANCE) > chance)
			return Collections.emptyList();

		double rate = baseRate * playerRate * mod;

		List<RewardItem> ret = new ArrayList<RewardItem>(_items.size());
		rollFinal(_items, ret, rate, Math.max(_chanceSum, RewardList.MAX_CHANCE));
		for(RewardItem i : ret)
			i.isAdena = true;

		return ret;
	}

	private void rollFinal(List<RewardData> items, List<RewardItem> ret, double mult, double chanceSum)
	{
		// перебираем все вещи в группе и проверяем шанс
		int chance = Rnd.get(0, (int) chanceSum);
		long count;
        //TODO нужны тесты этой системы
		for(RewardData i : items)
        {   // проверяем шанс
            if(chance < i.getChanceInGroup() && chance > i.getChanceInGroup() - i.getChance())
            {
                double imult = i.notRate() ? 1.0 : mult; //todo надо все же подумать над тем чтобы выпилить notrate..

                if(i.getMinDrop() > i.getMaxDrop()) { // если минимальное кол-во больше чем максимальное (ну а вдруг)
                    _log.warn("Item:"+i.getItem().getName()+" MinDrop > MaxDrop"); //в консоль пишем предупреждение
                    count = Math.round(i.getMaxDrop()); // даем максимальное количество
                }
                else if(i.getMinDrop() == i.getMaxDrop()) // если минимальное кол-во одинаково с максимальным
                    count = Rnd.get(Math.round(i.getMinDrop()), Math.round(i.getMaxDrop() * imult)); // берем рандом от минимального до максимального умноженного на наш рейт
                else
                    count = Rnd.get(Math.round(i.getMinDrop() * imult), Math.round(i.getMaxDrop() * imult)); //в остальных случаях берем рандом от умноженного на рейт минимального по максимальное

                RewardItem t = null;

				for(RewardItem r : ret)
					if(i.getItemId() == r.itemId)
					{
						t = r;
						break;
					}

				if(t == null)
				{
					ret.add(t = new RewardItem(i.getItemId()));
					t.count = count;
				}
                /*
				else if(!i.notRate())
				{
					t.count = SafeMath.addAndLimit(t.count, count);
				}
                */
				break;
			}
		}
	}
}